package org.roguelikedevelopment.data.gamedata;

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.filter.ElementFilter;
import org.jdom.input.SAXBuilder;
import org.roguelikedevelopment.dweller.gamedata.Colors;
import org.roguelikedevelopment.dweller.gamedata.GameDataConstants;

public class GameDataGenerator {

	public static void main(String[] args) {
		try {
			generate();
		}
		catch (JDOMException jdomex) {
			System.out.println(jdomex);
		}
		catch (IOException ioex) {
			System.out.println(ioex);
		}
	}

	/**
	 * Reads the message specifications from {@link #specificationFile}. Adds
	 * the read messages to {@link #messageTypes}. Also sets
	 * {@link #modificationTimeLimit} to the time when the message specification
	 * file was generated.
	 */
	public static void generate() throws JDOMException, IOException {
		GameDataGenerator g = new GameDataGenerator();
		SAXBuilder saxBuilder = new SAXBuilder();
		Document document = saxBuilder.build(new File("./res/entities.xml"));
		g.generateGameData(document.getRootElement().getChildren("class"));
	}

	/**
	 * Generates dweller binary data files from the game data xml
	 * @param classes
	 * @throws IOException
	 */
	private void generateGameData(List classes) throws IOException {
		System.out.println("Writing game data");
		// iterate over monsters, items and players
		for (Iterator cit = classes.iterator(); cit.hasNext();) {
			Element classElement = (Element) cit.next();
			String className = classElement.getAttributeValue("name").toLowerCase();
			System.out.println("\n\nWriting " + className + " data");
			if(className.equals("creature")) {
				writeMonsters(classElement);
			}
			else if(className.equals("item")) {
				writeItems(classElement);
			}
			else if(className.equals("player")) {
				writePlayers(classElement);
			}
			else {
				System.err.println("Unknown class " + className);
			}
			
		}
	}
	
	private void writeStartingInventory(Element entityElement, DataOutput output) throws IOException {
		System.out.println("\t\tWriting starting inventory for " + entityElement.getAttributeValue("name"));
		List inventory = entityElement.getChildren("inventory");
		output.writeByte(inventory.size());
		for (Iterator invit = inventory.iterator(); invit.hasNext();) {
			Element inventoryElement = (Element) invit.next();
			System.out.println("\t\t" + inventoryElement.getAttributeValue("type").toUpperCase());
			output.writeByte(GameDataConstants.getEntityTypeFromString(inventoryElement.getAttributeValue("type").toUpperCase()));
			output.writeByte(Byte.parseByte(getContent(inventoryElement, "0")));
			output.writeShort(Short.parseShort(getAttribute(inventoryElement, "count", "1")));
		}
	}
	
	private void writeEntity(Element entityElement, DataOutput output) throws IOException {
		System.out.println("\tWriting entity " + entityElement.getAttributeValue("name"));
		output.writeUTF(entityElement.getAttributeValue("name"));
		output.writeUTF(getContent(entityElement.getChild("description"), ""));
		output.writeByte(Byte.parseByte(entityElement.getAttributeValue("mindepth", "1")));
		output.writeByte(Byte.parseByte(entityElement.getAttributeValue("maxdepth", "1")));
		output.writeShort(Short.parseShort(entityElement.getAttributeValue("frequency", "1")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("weight"), "0")));
		output.writeShort(Short.parseShort(getContent(entityElement.getChild("amount"), "1")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("amount"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("attack"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("attack"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("defence"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("defence"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("magic"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("magic"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("speed"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("speed"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("hp"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("hp"), "mod", "0")));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("exp"), "0")));
		output.writeByte(Byte.parseByte(getAttribute(entityElement.getChild("exp"), "mod", "0")));
		output.writeByte(GameDataConstants.getEffectIdFromString(getContent(entityElement.getChild("spell"), "NONE")));		

		output.writeByte(Colors.getColorIdFromString(getAttribute(entityElement.getChild("glyph"), "color", "WHITE")));
		output.writeChar(getContent(entityElement.getChild("glyph"), "?").charAt(0));
		output.writeByte(Byte.parseByte(getContent(entityElement.getChild("tile"), "0")));

		output.writeByte(GameDataConstants.getAlignmentFromString(entityElement.getAttributeValue("alignment", "NEUTRAL")));

		// flags
		short flags = GameDataConstants.FLAG_NONE;
		List flagsList = entityElement.getChildren("flag");
		while(!flagsList.isEmpty()) {
			flags |= GameDataConstants.getFlagFromString(getContent((Element)flagsList.remove(0), "NONE"));
		}
		output.writeShort(flags);
		
		// themes
		byte themes = GameDataConstants.THEME_ALL;
		List themesList = entityElement.getChildren("themes");
		while(!themesList.isEmpty()) {
			themes |= GameDataConstants.getThemeFromString(getContent((Element)themesList.remove(0), "NONE"));
		}
		output.writeByte(themes);
	}
	
	private void writeMonsters(Element classElement) throws IOException {
		DataOutputStream output = new DataOutputStream(new FileOutputStream("../dwellercore/res/monsters.bin"));		
		output.writeShort(countElements(classElement));
		
		List types = classElement.getChildren("type");
		output.writeByte(types.size());
		// iterate over entity types (melee, trinket, shield)
		for (Iterator tit = types.iterator(); tit.hasNext();) {
			Element typeElement = (Element) tit.next();
			output.writeByte(GameDataConstants.getEntityTypeFromString(typeElement.getAttributeValue("name")));				

			List entities = typeElement.getChildren("entity");
			output.writeByte(entities.size());
			// iterate over entities
			for (Iterator eit = entities.iterator(); eit.hasNext();) {
				Element entityElement = (Element) eit.next();
				writeEntity(entityElement, output);
				writeStartingInventory(entityElement, output);
			}
		}
		output.flush();
		output.close();
	}
	
	private void writePlayers(Element classElement) throws IOException {
		DataOutputStream output = new DataOutputStream(new FileOutputStream("../dwellercore/res/players.bin"));		
		output.writeShort(countElements(classElement));
		
		List types = classElement.getChildren("type");
		output.writeByte(types.size());
		// iterate over entity types (melee, trinket, shield)
		for (Iterator tit = types.iterator(); tit.hasNext();) {
			Element typeElement = (Element) tit.next();
			output.writeByte(GameDataConstants.getEntityTypeFromString(typeElement.getAttributeValue("name")));				

			List entities = typeElement.getChildren("entity");
			output.writeByte(entities.size());
			// iterate over entities
			for (Iterator eit = entities.iterator(); eit.hasNext();) {
				Element entityElement = (Element) eit.next();
				writeEntity(entityElement, output);
				writeStartingInventory(entityElement, output);
			}
		}
		output.flush();
		output.close();
	}
	
	
	private void writeItems(Element classElement) throws IOException {
		DataOutputStream output = new DataOutputStream(new FileOutputStream("../dwellercore/res/items.bin"));		
		output.writeShort(countElements(classElement));
		
		List types = classElement.getChildren("type");
		System.out.println(types.size() + " item types");
		output.writeByte(types.size());
		// iterate over entity types (melee, trinket, shield)
		for (Iterator tit = types.iterator(); tit.hasNext();) {
			Element typeElement = (Element) tit.next();
			String typeName = typeElement.getAttributeValue("name");
			System.out.println("\t" + typeName);
			output.writeByte(GameDataConstants.getEntityTypeFromString(typeName));				

			// write effects for this type
			List effects = typeElement.getChildren("effect");
			output.writeByte(effects.size());
			System.out.println("\tItem type " + typeName + " has " + effects.size() + " effects");
			for (Iterator efit = effects.iterator(); efit.hasNext();) {
				Element effectElement = (Element) efit.next();
				String effectName = getContent(effectElement, "NONE");
				System.out.println("\t\tWriting " + typeName + " effect " + effectName);
				output.writeByte(GameDataConstants.getEffectIdFromString(effectName));		
				
			}

			List entities = typeElement.getChildren("entity");
			output.writeByte(entities.size());
			// iterate over entities
			for (Iterator eit = entities.iterator(); eit.hasNext();) {
				Element entityElement = (Element) eit.next();
				writeEntity(entityElement, output);
			}
		}
		output.flush();
		output.close();
	}
	
	
	private int countElements(Element root) {
		int count = 0;
		Iterator it = root.getDescendants(new ElementFilter("entity"));
		while(it.hasNext()) {
			it.next();
			count++;
		}
		return count;
	}
	
	/**
	 * Gets the content of an element
	 * @param element
	 * @param defaultContent
	 * @return
	 */
	public static String getContent(Element element, String defaultContent) {
		if(element != null) {
			if(element.getContentSize() > 0) {
				Content c=element.getContent(0);
				if(c != null) {
					return c.getValue();
				}
			}
		}
		return defaultContent;
	}
	
	/**
	 * Gets an attribute from an element
	 * @param element
	 * @param attribute
	 * @param defaultValue
	 * @return
	 */
	public static String getAttribute(Element element, String attribute, String defaultValue) {
		if(element != null) {
			return element.getAttributeValue(attribute, defaultValue);
		}
		return defaultValue;
	}	
}
